package com.danieltoms.gcSync;

import java.io.IOException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Vector;

import javax.microedition.pim.Contact;
import javax.microedition.pim.ContactList;
import javax.microedition.pim.PIM;
import javax.microedition.pim.PIMException;

import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.GaugeField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.container.DialogFieldManager;
import net.rim.device.api.ui.container.PopupScreen;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.Screen;
import net.rim.device.api.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import com.danieltoms.gcSync.contact.BBContact;
import com.danieltoms.gcSync.google.GoogleContact;
import com.danieltoms.gcSync.google.GoogleDataItem;
import com.danieltoms.gcSync.google.GoogleDataStore;
import com.danieltoms.gcSync.google.GoogleLoginException;
import com.danieltoms.gcSync.google.GoogleTypedField;
import com.danieltoms.gcSync.google.feed.GoogleContactFeed;
import com.danieltoms.gcSync.google.feed.GoogleCreateFeed;
import com.danieltoms.gcSync.google.feed.GoogleGetFeed;
import com.danieltoms.gcSync.google.feed.GoogleUpdateFeed;
import com.danieltoms.gcSync.screens.StatusScreen;
import com.danieltoms.gcSync.util.DateUtil;
import com.danieltoms.gcSync.util.NameUtil;

public class SyncThread extends Thread
{

	private GoogleDataStore _googleDataStore = null;
	private GcSyncStore _gcSyncStore = null;
	private ContactList _bbContacts = null;
	private String _updatedDate = null;

	private SyncStatus getSyncStatus()
	{
		return ((GcSync) UiApplication.getUiApplication()).getSyncStatus();
	}

	public SyncThread()
	{
		_googleDataStore = GoogleDataStore.instance();
		_gcSyncStore = GcSyncStore.instance();

	}

	public BBContact getBBContactByEmailOrName(String email, String name) throws PIMException
	{
		if (_bbContacts == null)
			_bbContacts = ((ContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE));

		// first look by email, google only allows one email per
		// contact, so we
		// enforce that for the bb on login and on contact add/update
		Contact c = _bbContacts.createContact();
		BBContact b = new BBContact(c);
		Enumeration e = null;
		BBContact ret = null;

		if (email != null && !email.equals(""))
		{
			b.addEmail(email);

			e = _bbContacts.items(c);

			// can't have the differnt contacts with teh same email
			// so we'll
			// only have one match
			// we found a match
			if (e.hasMoreElements())
			{

				// check we didn't find a google contact with
				// the same name
				// added previously in this sync
				b = new BBContact((Contact) e.nextElement());

				if (_googleDataStore.getSyncItemByUID(b.getUID()) == null)
				{
					ret = b;
				}
			}

		}
		else if (name != null && !name.equals(""))
		{

			// else search by name
			c = _bbContacts.createContact();
			b = new BBContact(c);

			b.setGivenName(name);

			e = _bbContacts.items(c);

			// we found a match
			while (e.hasMoreElements())
			{

				c = (Contact) e.nextElement();
				b = new BBContact(c);

				email = b.getEmail();

				// we only want name matches without email
				if (email == null || email.equals(""))
				{
					if (ret == null)
						ret = b;
					else
					{
						// we have multiple users with
						// the same name with no
						// email
						// save this list and force the
						// user to manually resolve
						// later
						// warn for now
						log("Multiple users with name: " + name);

						// we can throw an exxception
						// save a sync item with teh
						// google id and state
						// multiple
						// later we resolve multple
						// matches by

						// if google has multiple users
						// with same name, it will
						// match the first bb contact it
						// finds by name
						// we can't do much about that.
						/*
						 * 
						 * google bb 2 1 = match 1st bb 2 2 = match 1 and then
						 * 2... order is incosistent 3 2 = mat 1,2, then create
						 * new order is inconsistent 1 2 = matche 1, then create
						 * new google for other, order is inconsistent
						 * 
						 * maybe do a prescan before the sync to look for
						 * duplicate names and warn? yes!
						 * 
						 * 
						 */
					}

				}
			} // end while

		}

		return ret;
	}

	public BBContact getBBContactByUID(String uid) throws PIMException
	{
		if (_bbContacts == null)
			_bbContacts = (ContactList) PIM.getInstance().openPIMList(PIM.CONTACT_LIST, PIM.READ_WRITE);

		BBContact bc = new BBContact(_bbContacts.createContact());
		bc.setUID(uid);

		Enumeration e = _bbContacts.items(bc.getContact());

		if (e.hasMoreElements())
			return new BBContact((Contact) e.nextElement());

		return null;
	}

	private boolean _stopped = true;

	public void stop()
	{
		_stopped = true;
	}

	public void run()
	{
		_stopped = false;

		try
		{
			((GcSync) UiApplication.getUiApplication()).resetSyncStatus();
			sync();
			this.getSyncStatus().setSuccess(true);
		}
		catch (PIMException e)
		{
			e.printStackTrace();
			this.getSyncStatus().setStatusMessage("PIMException " + e.getMessage());
			this.getSyncStatus().setSuccess(false);
		}
		catch (GoogleLoginException e)
		{
			this.getSyncStatus().setStatusMessage(e.getMessage());
			this.getSyncStatus().setSuccess(false);
		}
		catch (IOException e)
		{
			this.getSyncStatus().setStatusMessage("Network Failure");
			this.getSyncStatus().setSuccess(false);
		}
		catch (ParserConfigurationException e)
		{
			this.getSyncStatus().setStatusMessage("ParserConfigurationException " + e.getMessage());
			this.getSyncStatus().setSuccess(false);
		}
		catch (SAXException e)
		{
			this.getSyncStatus().setStatusMessage("SAXException " + e.getMessage());
			this.getSyncStatus().setSuccess(false);
		}
		catch (Throwable t)
		{
			this.getSyncStatus().setStatusMessage("Exception " + t.getMessage());
			this.getSyncStatus().setSuccess(false);
		}

		((GcSync) UiApplication.getUiApplication()).resetSyncThread();
		log("end run thread...");
	}

	private void sync() throws PIMException, GoogleLoginException, IOException, ParserConfigurationException,
	        SAXException
	{
		log("Start sync");
		

		// disable the contact listener while we run as we'll be making
		// updates
		_gcSyncStore.setContactListenerEnabled(false);

		boolean initialSync = false;

		String lastSync = _gcSyncStore.getLastSyncDate();

		if (lastSync == null || lastSync.equals(""))
		{
			// this is an initial sync

			initialSync = true;
			_googleDataStore.initGoogleDataStore();

		}

		GoogleContactFeed feed = new GoogleContactFeed(lastSync);

		syncGoogle(feed, initialSync);
		syncBlackBerry(feed);

		// if we loaded this clear it

		_bbContacts = null;

		// TODO thi was when I allowed manual stopping, which I don't think i'll allow.
		_stopped = false;
		if (!_stopped)
		{
			// todo: handle time zone and google time issues
			log("successful run, storing");
			_gcSyncStore.storeLastSyncDate(_updatedDate);
		}
		else
		{
			log("stopped run");
			// store interrupted state
			// don't store sync data, so next time it resyncs
		}

		_gcSyncStore.commit();
		_googleDataStore.commit();
		_gcSyncStore.setContactListenerEnabled(true);
	}

	private void syncGoogle(GoogleContactFeed feed, boolean initialSync) throws PIMException, GoogleLoginException,
	        IOException, ParserConfigurationException, SAXException
	{

		int items = 0;

		int conflictOption = _gcSyncStore.getConflictOption();

		this.getSyncStatus().setStatusMessage("Downloading contacts...");

		Vector googleContacts = feed.fetchGoogleContacts();
		_updatedDate = feed.getUpdatedString();
		
		if (_stopped)
			return;

		if (googleContacts == null || googleContacts.size() == 0)
			return;

		this.getSyncStatus().setCurrentItems(0);
		this.getSyncStatus().setTotalItems(feed.getTotalResults());
		// reset needs to be set after any progress bar statuses
		this.getSyncStatus().setResetProgressBar(true);

		this.getSyncStatus().setStatusMessage("Syncing...");

		while (googleContacts != null)
		{


			// sync google first.
			Enumeration e = googleContacts.elements();

			// this will merge or add new contacts
			while (e.hasMoreElements())
			{
				if (_stopped)
					break;

				items++;

				this.getSyncStatus().setCurrentItems(items);

				log("Number of google contacts: " + googleContacts.size());

				GoogleContact gc = (GoogleContact) e.nextElement();

				log("Processing google contact id: " + gc.getId() + "-" + gc.getPrimaryEmail() + "-" + gc.getName());

				GoogleDataItem si = _googleDataStore.getSyncItemByGoogleID(gc.getId());

				// this item has never been synced, find by
				// email or name
				if (si == null && !gc.deleted())
				{

					BBContact bc = getBBContactByEmailOrName(gc.getPrimaryEmail(), gc.getName());

					if (bc == null) // new contact from
					// google
					{
						createBBContact(gc);
					}
					else
					{
						log("Matched merge");
						// matched a bbcontact, now sync
						// up
						mergeBBContact(gc, bc, conflictOption, feed);
					}

				}
				else
				// this contact has been synced before
				{
					// first check

					// we can still have deleted elements
					// here on an intial
					// sync, make sure we have an si
					if (si != null)
					{
						BBContact bc = getBBContactByUID(si.getBBUID());

						if (gc.deleted())
						{
							// deletion always wins.
							// doesn't matter if
							// this has
							// a pending bb update
							if (bc != null)
							{

								this.getSyncStatus().bbDelete();
								bc.delete();
								_googleDataStore.removeGoogleDataItem(si);
							}

						}
						else
						{

							// remember we are only
							// getting google
							// contacts here
							// from the last sync
							// date

							if (si.getState() == GoogleDataItem.BB_UPDATED)
							{
								// houston, we
								// have a
								// conflict

								if (conflictOption == GcSyncConstants.OPTION_CONFLICT_BLACKBERRY)
								{

									// todo:
									// test
									updateGoogleContact(si, bc);
								}
								else if (conflictOption == GcSyncConstants.OPTION_CONFLICT_GOOGLE)
								{
									// todo:
									// test
									// log
									// the
									// connflict
									updateBBContact(gc, bc, si);
									si.setState(GoogleDataItem.NA);
									si.setUpdatedDate(new Date());

								}
							}
							else
							// no conflict
							{
								// this is a
								// google
								// update, but
								// if the
								// updated
								// date
								// of the si is
								// more recent,
								// then we skip
								// it. we
								// already
								// updated this
								// contact
								// in the last
								// sync

								log("si date: " + si.getUpdatedDate() + " long: " + si.getUpdatedDate().getTime());
								log("gc date: " + DateUtil.parseRFC3339(gc.getUpdated()) + " long: "
								        + DateUtil.parseRFC3339(gc.getUpdated()).getTime());

								if (si.getUpdatedDate().getTime() < DateUtil.parseRFC3339(gc.getUpdated()).getTime())
								{
									updateBBContact(gc, bc, si);

								}

							}
						}
					}
				}
				log("end processing google contact id: " + gc.getId());

			} // end google

			if (_stopped)
				break;

			this.getSyncStatus().setStatusMessage("Downloading contacts...");

			googleContacts = feed.fetchGoogleContacts();

			this.getSyncStatus().setStatusMessage("Syncing...");
		}

	}

	private void updateBBContact(GoogleContact gc, BBContact bc, GoogleDataItem si) throws PIMException
	{

		copyGoogletoBB(gc, bc, si);
		this.getSyncStatus().bbUpdate();

	}

	private void createBBContact(GoogleContact gc) throws PIMException
	{
		BBContact bc = new BBContact(_bbContacts.createContact());

		GoogleDataItem si = new GoogleDataItem();
		copyGoogletoBB(gc, bc, si);
		si.setBBUID(bc.getUID());
		si.setGoogleId(gc.getId());
		log("Setting state: " + new Integer(GoogleDataItem.NA).toString());
		si.setState(GoogleDataItem.NA);
		log("get state:" + si.getState());
		si.setUpdatedDate(new Date());

		_googleDataStore.addGoogleDataItem(si);

		this.getSyncStatus().bbAdd();

	}

	private void copyGoogletoBB(GoogleContact gc, BBContact bc, GoogleDataItem gdi) throws PIMException
	{
		gdi.setEmails(gc.getEmails());
		gdi.setAddresses(gc.getAddresses());
		gdi.setPhones(gc.getPhones());
		gdi.setIms(gc.getIms());

		bc.setCompany(gc.getCompany());
		bc.setJobTitle(gc.getJobTitle());
		// bc.setEmail(gc.getPrimaryEmail());

		// add first 3 emails to bb
		Vector emails = gc.getEmails();
		if (emails != null)
		{
			Enumeration e = emails.elements();
			if (e != null)
			{
				int i = 0;
				while (e.hasMoreElements() && i < 3)
				{
					i++;
					GoogleTypedField gtf = (GoogleTypedField) e.nextElement();
					bc.addEmail(gtf.getValue());
				}
			}
		}

		NameUtil.setBBName(NameUtil.GIVEN_FAMILY, NameUtil.SPLIT_TO_GIVEN, bc, gc);
		bc.setNotes(gc.getNotes());
		bc.commit();
	}

	// called on initial sync when we've matched a bb contact with the same
	// name
	// and email or same name as the incoming google contact
	// that has never been synced before.
	private void mergeBBContact(GoogleContact gc, BBContact bc, int conflictOption, GoogleContactFeed feed)
	        throws PIMException, ParserConfigurationException, SAXException
	{

		// bc.setEmail(gc.getPrimaryEmail());
		bc.setGivenName(bc.getGivenName());

		GoogleDataItem si = new GoogleDataItem();

		if (conflictOption == GcSyncConstants.OPTION_CONFLICT_GOOGLE)
		{
			bc.setNotes(gc.getNotes());
			si.setUpdatedDate(new Date());
		}
		else if (conflictOption == GcSyncConstants.OPTION_CONFLICT_BLACKBERRY)
		{
			gc.setNotes(bc.getNotes());

			GoogleUpdateFeed updateFeed = new GoogleUpdateFeed();
			gc = updateFeed.updateContact(gc);
			this.getSyncStatus().googleUpdate();
			si.setUpdatedDateByRFC3339(gc.getUpdated());
		}

		bc.commit();

		si.setBBUID(bc.getUID());
		si.setGoogleId(gc.getId());
		si.setState(GoogleDataItem.NA);
		_googleDataStore.addGoogleDataItem(si);
		// TODO this whole area needs some work for merge
		this.getSyncStatus().bbAdd();
	}

	private void copyBBtoGoogle(GoogleContact gc, BBContact bc, GoogleDataItem gdi)
	{
	//	NameUtil.setGoogleName(nameFormat, gc, bb) .setBBName(NameUtil.GIVEN_FAMILY, NameUtil.SPLIT_TO_GIVEN, bc, gc);
		gc.setEmails(gdi.getEmails());
		gc.setAddresses(gdi.getAddresses());
		gc.setPhoneNumbers(gdi.getPhones());
		gc.setInstantMessages(gdi.getIms());
		
		gc.setNotes(bc.getNotes());
	}

	private void syncBlackBerry(GoogleContactFeed feed) throws GoogleLoginException, IOException,
	        ParserConfigurationException, SAXException, PIMException
	{

		if(_googleDataStore.getPendingBBSyncCount()!=0)
		{
			Enumeration syncMappingItems = _googleDataStore.getSyncMappings();
			
			this.getSyncStatus().setCurrentItems(0);
			
			//TODO track the total changed items so we can report this better
			this.getSyncStatus().setTotalItems(_googleDataStore.getPendingBBSyncCount());
			
			log("total pending bb items: " + this.getSyncStatus().getTotalItems());
			// reset needs to be set after any progress bar statuses
			this.getSyncStatus().setResetProgressBar(true);
	
			this.getSyncStatus().setStatusMessage("Uploading BlackBerry Contacts ...");
	
			int count = 0;
			
	
			while (syncMappingItems.hasMoreElements())
			{
				if (_stopped)
					break;
				
				
				GoogleDataItem si = (GoogleDataItem) syncMappingItems.nextElement();
				BBContact bc = getBBContactByUID(si.getBBUID());
	
				if (si.getState() == GoogleDataItem.BB_ADD)
				{
					log("add");
					count++;
					this.getSyncStatus().setCurrentItems(count);
					createGoogleContact(si, bc);
				}
				else if (si.getState() == GoogleDataItem.BB_UPDATED)
				{
					log("update");
					// any updated conflicts would have been
					// resolved in the google
					// sync first
					count++;
					this.getSyncStatus().setCurrentItems(count);
					updateGoogleContact(si, bc);
	
				}
				else if (si.getState() == GoogleDataItem.BB_DELETED)
				{
					log("delete");
					// deletion always wins, doesn't matter what the
					// conflict option
					// is about
					count++;
					this.getSyncStatus().setCurrentItems(count);
					deleteGoogleContact(si);
	
				}
			}
		}

	}

	private void createGoogleContact(GoogleDataItem si, BBContact bc) throws GoogleLoginException, IOException,
	        ParserConfigurationException, SAXException
	{

		this.getSyncStatus().googleAdd();
		// get current google object by id from feed
		// then update
		GoogleCreateFeed gf = new GoogleCreateFeed();

		GoogleContact gc = new GoogleContact();

		gc.setNameGiven(bc.getGivenName());

		gc = gf.createContact(gc);
		// copyBBtoGoogle(gc, bc);

		si.setState(GoogleDataItem.NA);

		si.setUpdatedDateByRFC3339(gc.getUpdated());

	}

	private void updateGoogleContact(GoogleDataItem si, BBContact bc) throws GoogleLoginException, IOException,
	        ParserConfigurationException, SAXException
	{

		this.getSyncStatus().googleUpdate();
		// get current google object by id from feed
		// then update
		GoogleGetFeed gf = new GoogleGetFeed();

		GoogleContact gc = gf.fetchContact(si.getGoogleId());

		gc.prettyPrintXML(System.out);

		copyBBtoGoogle(gc, bc, si);

		GoogleUpdateFeed uf = new GoogleUpdateFeed();
		gc = uf.updateContact(gc);

		si.setState(GoogleDataItem.NA);

		si.setUpdatedDateByRFC3339(gc.getUpdated());

	}

	private void deleteGoogleContact(GoogleDataItem si) throws GoogleLoginException, IOException,
	        ParserConfigurationException, SAXException, PIMException
	{
		this.getSyncStatus().googleDelete();

		GoogleGetFeed gf = new GoogleGetFeed();

		GoogleContact gc = gf.fetchContact(si.getGoogleId());

		GoogleUpdateFeed uf = new GoogleUpdateFeed();
		uf.deleteContact(gc);

		_googleDataStore.removeGoogleDataItem(si);

	}

	private void log(String message)
	{
		System.out.println(message);

	}

}
